//--------------------------------------------------------------------------------
/*!
\file	LowLevelCore.cpp
\author	bsmith, dkarp
\date	02/21/07
\brief	Manages and updates sounds and channels
\par	license
Copyright (c) DigiPen Institute of Technology 2006. All Rights Reserved. 
*/
//--------------------------------------------------------------------------------
#include "stdafx.h"
#include "LowLevel.h"
#include <fstream>
#include <direct.h>

using namespace sndcore;


bool LowLevelCore::FileExists(const WCHAR* file, unsigned& soundID)
{
	// Please don't read below this line. Thank you.

	std::wstring wFile(file);
	std::string cFile;
	for( unsigned i=0; i < wFile.length(); ++i )
		cFile += (char)wFile[i];
	
	SOUND_MAP::iterator itt = mSounds.begin();
	for( ; itt!=mSounds.end(); ++itt )
	{
		// HACK HACK
		std::string cPath;
		for( unsigned i=0; i < itt->second->mStrPath.length(); ++i )
			cPath += (char)itt->second->mStrPath[i];

		if( !strcmp(cPath.c_str(),cFile.c_str()) )
		{
			soundID = itt->first;
			return true;
		}
	}

	return false;
}

//-------------------------------------------------------------------------------
/*!
\brief Create the primary buffer 

\param for_tool
  boolean to change creation type of primary buffer

\return error returns false

\author	Danny Karp
*/
//-------------------------------------------------------------------------------
bool LowLevelCore::CreatePrimaryBuffer( bool for_tool )
{
	WAVEFORMATEX wfx;
	ZeroMemory( &wfx, sizeof(WAVEFORMATEX) );
	wfx.wFormatTag      = (WORD) WAVE_FORMAT_PCM;
	wfx.nChannels       = (WORD) 2;
	wfx.nSamplesPerSec  = (DWORD)44100;
	wfx.wBitsPerSample  = (WORD) 16;
	wfx.nBlockAlign     = (WORD) (wfx.wBitsPerSample / 8 * wfx.nChannels);
	wfx.nAvgBytesPerSec = (DWORD) (wfx.nSamplesPerSec * wfx.nBlockAlign);

	DSBUFFERDESC dsbd = {0};
	dsbd.dwSize = sizeof(dsbd);
	dsbd.dwBufferBytes = for_tool ? DSBSIZE_MAX : 0;
	dsbd.lpwfxFormat = for_tool ? &wfx : 0;
	dsbd.dwFlags = for_tool ? 0 : SCF_PRIMARY;

	LPDIRECTSOUNDBUFFER rawBuffer = NULL;
	HRESULT hr = mDSoundInterface->CreateSoundBuffer(&dsbd, &rawBuffer, NULL);

	if( FAILED( hr ) )
		return false;

	if (!for_tool) {
		hr = rawBuffer->SetFormat(&wfx);
		if( FAILED( hr ) )
			return false;
	}

	mPrimaryBuffer = new SoundBufferDS(rawBuffer, wfx);

	return true;
}
//-------------------------------------------------------------------------------
/*!
\brief Loads a regular sound

\param wav_path
   path for wave

\param soundId
   returned sound id

\param pitchSteps
    number of octaves up or down that you want to go
\return error returns false

\author	Danny Karp
*/
//-------------------------------------------------------------------------------
bool LowLevelCore::LoadSound( const WCHAR * wav_path, unsigned& soundId, float pitchSteps )
{

	//assert( mPrimaryBuffer != NULL && "PRIMARY BUFFER IS NULL DEBUGGING Y'ALL" );

	Sound *converted_sound = NULL;

	      //this fix ReadFileEx from not working 
	  //providing the full path fixes it
	WCHAR buffer[_MAX_PATH];
	_wgetcwd( buffer, _MAX_PATH );
	std::wstring full_path( buffer );
	full_path += std::wstring(L"\\");
	full_path += wav_path;

	CWaveFile new_wav;

	if( FAILED( new_wav.Open( full_path.c_str(), NULL, WAVEFILE_READ ) ) )
		return false;

	BYTE *buff1 = new BYTE[ new_wav.GetSize() ];
	DWORD cbRead;

	if( FAILED( new_wav.Read( buff1, new_wav.GetSize(), &cbRead) ) )
		return false;

	//resample this shart so we can not worry about resampling every time we want to grab data
	SoundBufferOptions opts;
	opts.format_ = *new_wav.GetFormat(); 
	opts.size_ =   new_wav.GetSize();
	  //get song length in milliseconds
	opts.songlen_ = int( ( new_wav.GetSize() / float( opts.format_.nAvgBytesPerSec ) ) * 1000 );    

	SoundBufferUser * newBuffer = new SoundBufferUser( opts );

	newBuffer->WriteBuffer( new_wav.GetSize(), buff1 );

	soundId = ++mSoundId;
	Sound oldSound( newBuffer, false,  soundId);

	//resample the sound plz
	mPrimaryBuffer->GetSize();
	LowLevelResample( oldSound, &mPrimaryBuffer->GetFormat(),  pitchSteps, &converted_sound);
	
	assert( converted_sound != NULL );
	
	converted_sound->mStrPath = wav_path;

	mSounds[soundId] = converted_sound;

	SAFE_DELETE_ARRAY(buff1);
	new_wav.Close();

	return true;
}
//-------------------------------------------------------------------------------
/*!
\brief Loads a streaming sound

\param wav_path
path for wave

\param soundId
returned sound id

\param pitchSteps
number of octaves up or down that you want to go
\return error returns false

\author	Danny Karp
*/
//-------------------------------------------------------------------------------
bool LowLevelCore::LoadStream( const WCHAR * wav_path, unsigned& soundId, float pitchSteps )
{

      //this fix ReadFileEx from not working 
	  //providing the full path fixes it
	WCHAR buffer[_MAX_PATH];
	_wgetcwd( buffer, _MAX_PATH );
	std::wstring full_path( buffer );
	full_path += std::wstring(L"\\");
	full_path += wav_path;

	CWaveFile new_wav;

	HRESULT hr = new_wav.Open( full_path.c_str(), NULL, WAVEFILE_READ );
	if( hr != S_OK )
		return NULL;

	DWORD errors = GetLastError();

	SetLastError( 0 );

	DWORD dwDataOffset = new_wav.GetDataOffset();

	//// bytes per second / 1000 == bytes per ms
	DWORD dwBufferSize = mWritePerFrame;//( STREAM_BUFFER_MILLISECONDS * new_wav.GetFormat()->nAvgBytesPerSec ) / 1000; 


	 //resampling every time we want to grab data
	SoundBufferOptions opts;
	opts.format_ = *new_wav.GetFormat(); 
	opts.size_ =   dwBufferSize;
      //get song length in milliseconds
	opts.songlen_ = int( ( new_wav.GetSize() / float( opts.format_.nAvgBytesPerSec ) ) * 1000 );    


	SoundBufferUser * newBuffer = new SoundBufferUser( opts, mWritePerFrame );

	new_wav.Close();
	newBuffer->m_hStreamFile = CreateFile( 
		full_path.c_str(), 
		GENERIC_READ, 
		FILE_SHARE_READ, 
		NULL, 
		OPEN_ALWAYS, 
		FILE_FLAG_OVERLAPPED, // specifies that we'll use "overlapped" (asynchronous) I/O
		NULL );

	errors = GetLastError();

	SetLastError( 0 );

	errors = GetLastError();

	if ( newBuffer->m_hStreamFile == INVALID_HANDLE_VALUE ) 
		return NULL;

	//
	// Start an async read operation
	//
	if( hr == S_OK )
	{

		memset( &newBuffer->m_streamFileStatus, 0, sizeof( OVERLAPPED ) );
		newBuffer->m_streamFileStatus.Offset = dwDataOffset;
		newBuffer->mlBeginOffset = dwDataOffset;
		BOOL ret = ReadFileEx( newBuffer->m_hStreamFile, newBuffer->m_streamFileBuf,
			newBuffer->mStreamBuffSize, &newBuffer->m_streamFileStatus, NULL );

		/*DWORD err = GetLastError( );*/


		// set the file buffer pointer to its maximum value--this is our indication that
		// the file buffer needs more data from the file.
		newBuffer->mlFileReadCursor = mWritePerFrame;
		newBuffer->mlFileTotalByteRead = mWritePerFrame;
	}

	soundId = mSoundId++;
	Sound * oldSound = new Sound( newBuffer, true, soundId );

	oldSound->mSavePitchSteps = pitchSteps;

	oldSound->mPrimaryFormat = mPrimaryBuffer->GetFormat();

	oldSound->mStreamTotalDataSize = new_wav.GetSize() - mWritePerFrame; /// ( new_wav.GetFormat()->wBitsPerSample >> 3 );

	assert( oldSound != NULL );

	mSounds[soundId] = oldSound;

	return true;
}
//-------------------------------------------------------------------------------
/*!
\brief Sound update


\author	Danny Karp
*/
//-------------------------------------------------------------------------------
void LowLevelCore::Update( )
{
	if( !mPrimaryBuffer )
		return;

	HRESULT hr = S_OK; 
	DWORD play_cursor;
	hr = mPrimaryBuffer->GetCursorPosition( &play_cursor , NULL );

	static bool sync = false;

	if( !sync )
	{
		mWriteCursor = play_cursor + mWritePerFrame;
		sync = true;
	}

	const unsigned nextWrite = (mWriteCursor + mWritePerFrame ) % mPrimaryBuffer->GetSize();
	const unsigned byteBuffer = (play_cursor - nextWrite) % mPrimaryBuffer->GetSize();

	//if the cursor is gonna pass the play cursor than no update
	if( byteBuffer <= mWritePerFrame) 
	{
		return;
	}

	//flush the mixer
	//todo: mixer only as big as write_per_frame
	if( mMixer->GetBuffer()->GetFormat().wBitsPerSample == 16 )
	{
		mMixer->Reset( 0 );
	}
	else if( mMixer->GetBuffer()->GetFormat().wBitsPerSample == 8 )
	{
		mMixer->Reset( 128 );
	}
	static float dir = 1.0f;
	
	  //go through each channel
	for( CHANNEL_MAP_IT channel_it = mChannels.begin(); channel_it != mChannels.end(); ) 
	{
		 //PAN TEST
		  //update pan
		Channel * cur_channel = ( channel_it->second );

		if (cur_channel->Paused())
			continue;

		//float cur_pan = cur_channel->GetGain();

		//cur_pan = cur_pan + ( 0.0001f * dir );

		//if( cur_pan > 1.0f )
		//{
		//	cur_pan = 1.0f;
		//	dir = -1.0f;
		//}

		//else if( cur_pan < 0.0f) 
		//{
		//	cur_pan = 0.0f;
		//	dir = 1.0f;
		//}
		//cur_pan=0.5f;

		//cur_channel->SetGain( cur_pan );


		if( cur_channel->GetSound()->GetStream() )
		{
			cur_channel->AsyncUpdate();
		}

		  //start at the cursor position and cursor and mix num bytes
		if (mMixer->MixIn( cur_channel, mWritePerFrame, mVolume )) 
		{
			SAFE_DELETE( cur_channel );
			channel_it = mChannels.erase(channel_it);
			continue;
		}
		++channel_it;
	}	

	  //pass to primary buffer
	hr = mPrimaryBuffer->WriteBuffer( mWritePerFrame, mMixer->GetBuffer()->DirectRead(NULL), mWriteCursor  );
      //if we can write than update the write cursor 
	mWriteCursor = nextWrite;
}
//-------------------------------------------------------------------------------
/*!
\brief initialize sound system

\param wind
   handle to the window

\param for_tool
    boolean for tool

\return error returns false

\author	Danny Karp
*/
//-------------------------------------------------------------------------------

bool LowLevelCore::Initialize( HWND wind, bool for_tool )
{
	HRESULT hr = S_OK;
	  //Init DSound

	if( FAILED( DirectSoundCreate8(NULL, &mDSoundInterface, NULL ) ) )
	{
		return false;
	}

	DWORD coop_flag = for_tool ? DSSCL_PRIORITY : DSSCL_WRITEPRIMARY;
	if( FAILED( hr = mDSoundInterface->SetCooperativeLevel( wind , coop_flag) ) )
	{
		std::ofstream out;
		out.open(L"debug.txt");
		out << hr ;//<< L" ERROR";
		out.close();
		return false;
	}

	if( !CreatePrimaryBuffer(for_tool) ) {
		return false;
	}

	  //create the Mixer
	mWritePerFrame = mWritePerFrame + ( 4 - ( mWritePerFrame%4 ) );
	mMixer = new MiniMixer( mPrimaryBuffer, mWritePerFrame );

	  //start the primary buffer
	if (!for_tool)
		mPrimaryBuffer->Play( SPF_LOOP );

	return true;
}
//-------------------------------------------------------------------------------
/*!
\brief shutdown sound system


\author	Danny Karp
*/
//-------------------------------------------------------------------------------

void LowLevelCore::Shutdown( )
{
      //free channels
	for( CHANNEL_MAP_IT channel_it = mChannels.begin(); channel_it != mChannels.end(); ++channel_it )
	{

		SAFE_DELETE( channel_it->second );
	}
      //free sounds
	for( SOUND_MAP_IT sound_it = mSounds.begin(); sound_it != mSounds.end(); ++sound_it )
	{
		SAFE_DELETE( sound_it->second );
	}

	SAFE_DELETE( mPrimaryBuffer );
	SAFE_DELETE( mMixer );
	SAFE_RELEASE( mDSoundInterface );
}
//-------------------------------------------------------------------------------
/*!
\brief gets a sound from the sound passed in

\param soundId
sound to get

\return sound ptr

\author	Danny Karp
*/
//-------------------------------------------------------------------------------
Sound*	LowLevelCore::GetSound( unsigned soundId )
{
	//Search for the sound using the id
	SOUND_MAP_IT findIt = mSounds.find(soundId);
	if (findIt == mSounds.end())
		return NULL;
	return findIt->second;

}
//-------------------------------------------------------------------------------
/*!
\brief plays a loaded sound

\param soundId
 sound to play

\param channelId
 channel returned

\return channel ptr

\author Danny Karp
*/
//-------------------------------------------------------------------------------

Channel* LowLevelCore::PlaySound( unsigned soundId , unsigned* channelId )
{
	//Search for the sound using the id
	SOUND_MAP_IT findIt = mSounds.find(soundId);
	if (findIt == mSounds.end())
		return NULL;

	//Inc and set id
	unsigned tempId = mChannelId++;
	if (channelId)
		*channelId = tempId;

	//Make the new channel
	Channel * newChannel = new Channel( findIt->second, this );
	mChannels[tempId] = newChannel;
	return newChannel;
}
//-------------------------------------------------------------------------------
/*!
\brief gets a channel from the channel passed in

\param channelId
channel to get

\return channel ptr

\author	Danny Karp
*/
//-------------------------------------------------------------------------------

Channel* LowLevelCore::GetChannel( unsigned channelId )
{
	//Search for the sound using the id
	CHANNEL_MAP_IT findIt = mChannels.find(channelId);
	if (findIt == mChannels.end())
		return NULL;
	return findIt->second;
}

//-------------------------------------------------------------------------------
/*!
\brief stops a channel from the channel passed in

\param channelId
channel to stop

\author	Danny Karp
*/
//-------------------------------------------------------------------------------

void LowLevelCore::StopChannel(unsigned channelId)
{
	CHANNEL_MAP_IT findIt = mChannels.find(channelId);
	if (findIt == mChannels.end())
		return;
	SAFE_DELETE(findIt->second);
	mChannels.erase(findIt);
}
//-------------------------------------------------------------------------------
/*!
\brief release sound

\param soundId
sound id  to release

\author	Danny Karp
*/
//-------------------------------------------------------------------------------
void LowLevelCore::ReleaseSound(unsigned soundId)
{
	//Stop all channels playing the sound
	for (CHANNEL_MAP_IT it = mChannels.begin(); it != mChannels.end();) {
		if (it->second->SoundIs(soundId)) {
			SAFE_DELETE(it->second);
			it = mChannels.erase(it);
			continue;
		}
		++it;
	}

	//Delete the sound itself
	SOUND_MAP_IT findIt = mSounds.find(soundId);
	if (findIt == mSounds.end())
		return;
	SAFE_DELETE(findIt->second);
	mSounds.erase(findIt);
}
